# -*- coding: utf-8 -*-
"""
Module Description: 管理redis连接
"""
import time
from framework.firefly_dc import runtime_logger as log
from redis import StrictRedis, ConnectionPool
from common import service_config


class AutoDisconnectPool(ConnectionPool):
    """
    自动断开过剩连接的redis连接池
    相关调整：
       1.继承原有连接池类，增加以下属性:
            默认最小时间间隔（DEFAULT_INTERVAL）
            空闲连接稳定时间（conn_stable_ts）
            最小时间间隔（min_interval）
            最小连接数（min_connections）
       2.增加 初始化空闲连接的方法
       3.重写 创建连接 和 释放连接 的方法

    工作机制：
       1.初始化时，配置信息中包含 min_connections 参数，则根据最小连接数创建空闲连接（_available_connections）。
       2.redis请求并发:
            由低到高并且用完空闲连接列表时尝试创建新的连接，
            由高到低并且填满空闲连接列表时尝试断开连接
       3.创建连接时，已创建连接小于最大连接数允许创建
       4.释放连接时，并发进入下滑趋势时，断开过剩的连接，否则连接返回池中
         下滑趋势: 离开空闲连接稳定区（空闲连接数<最小连接数）一定时间
    """

    DEFAULT_INTERVAL = 300

    def __init__(self, min_interval=DEFAULT_INTERVAL, min_connections=None, **kwargs):
        ConnectionPool.__init__(self, **kwargs)
        # 增加 空闲连接稳定时间 、最小时间间隔 、 最小连接数
        self.conn_stable_ts = time.time()
        self.min_interval = min_interval
        self.min_connections = min_connections if min_connections else (self.max_connections / 4)

    def release(self, connection):
        """释放连接，并发处于下滑趋势时断开连接，否则该返池中"""
        self._checkpid()
        if connection.pid != self.pid:
            return
        self._in_use_connections.remove(connection)

        # 空闲连接过少（空闲连接稳定区）
        if len(self._available_connections) < self.min_connections:
            self._available_connections.append(connection)
            self.conn_stable_ts = time.time()

        # 离开空闲连接稳定区的时间过短
        elif (time.time() - self.conn_stable_ts) < self.min_interval:
            self._available_connections.append(connection)

        # 断开当前连接
        else:
            connection.disconnect()
            self._created_connections -= 1

    def init_idle_connections(self, init_command="PING"):
        """初始化空闲连接"""
        init_connections = []
        for _ in range(self.min_connections):

            # 获取连接对象（优先从空闲连接池中获取，如没有可用连接，则新建连接）
            connection = self.get_connection(None)

            # 发送请求（连接真正建立）
            connection.send_command(init_command)

            # 解析响应
            connection.read_response()

            # 添加该连接至空闲连接列表
            init_connections.append(connection)

        # 释放连接，填充空闲连接列表
        map(self.release, init_connections)


class RedisStorage(object):
    """redis连接管理类"""

    # 保存各redis的连接
    redis_conn_map = {}

    @classmethod
    def init(cls, need_conn_name_list=None, force_init_conn=False):
        """
        连接初始化
        :param need_conn_name_list: 指定需要初始化的连接.某些进程不需要建立所有的redis连接.传空则创建所有连接.
        :param force_init_conn: 是否强制初始化连接(在redis连接参数里含min_connections的情况下有效)
        """
        # v3.1 之后 cacheconf 必须包含 venus 系列的redis
        # venus_name_list = ["venus_extra", "venus_rank"]
        # for venus_name in venus_name_list:
        #     if venus_name not in service_config.config['cacheconf']:
        #         raise RuntimeError('cacheconf配置未包含{venus_name}'.format(venus_name=venus_name))

        for conn_name, conn_setting in service_config.config['cacheconf'].iteritems():
            if need_conn_name_list is not None and conn_name not in need_conn_name_list:
                continue

            if not isinstance(conn_setting, dict):
                continue

            # 默认开启连接池
            coon_pool = AutoDisconnectPool(**conn_setting)
            conn = StrictRedis(connection_pool=coon_pool)
            cls.redis_conn_map[conn_name.lower()] = conn

            # 强制初始化连接
            if force_init_conn and 'max_connections' in conn_setting:
                conn.connection_pool.init_idle_connections()
                init_conn_num = conn.connection_pool.min_connections
                log.info('Init redis {conn_name} conn num = {init_conn_num}'.format(conn_name=conn_name,
                                                                                           init_conn_num=init_conn_num))
            
    @classmethod
    def get_conn(cls, conn_name):
        """
        获取redis连接
        :param conn_name: 连接的名称
        """
        conn = cls.redis_conn_map.get(conn_name)
        return conn

    @classmethod
    def close_all(cls):
        """关闭所有redis连接"""
        for conn in cls.redis_conn_map.values():
            conn.connection_pool.disconnect()
